﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;

namespace Mind.Data
{
    public abstract class Wrapper
    {
        private string _tableName = null;
        string _keyName = null;
        bool _hasIdentity = false;
        bool _isNew = true;

        /// <summary>
        /// Verdadeiro se a classe foi populada usando FromDataRow ou FromDataReader
        /// e algum campo faltou para preencher a classe.
        /// </summary>
        public virtual bool SelectIncompleto { get; protected set; }

        #region Converter para formatos Úteis
        public virtual void FromDataRow(DataRow dataRow)
        {
            ModelFromDataRow(dataRow, this);
        }

        public virtual bool FromDataReader(DbDataReader reader)
        {
            return ModelFromDataReader(reader, this);
        }

        public virtual void ToDataRow(DataRow dataRow)
        {
            ModelToDataRow(this, dataRow);
        }

        public static void ModelFromDataRow(DataRow dataRow, Object model)
        {
            Wrapper mbObj = model as Wrapper;
            if (mbObj != null)
                mbObj.IsNew = false;

            foreach (PropertyInfo pi in model.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperPropertyAttribute), false).Length > 0
                    || pi.GetCustomAttributes(typeof(WrapperCalcPropertyAttribute), false).Length > 0)
                {
                    if (dataRow.Table.Columns.Contains(pi.Name))
                    {
                        object value = dataRow[pi.Name] is DBNull ? null : dataRow[pi.Name];

                        if (DataAccessManager.Default.ProviderName.Contains("SQLite"))
                        {
                            if (value is Int64) //&& pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false).Length > 0)
                            {
                                value = Convert.ToInt32((Int64)value);
                            }

                            if (value is Double)
                            {
                                value = Convert.ToDecimal((Double)value);
                            }

                        }

                        pi.SetValue(model, value, null);
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine(String.Format("{0} contém WrapperProperty {1} mas o select não continha este campo. Esta instância não poderá ser salva.", model.GetType().Name, pi.Name));
                        if (model is Wrapper)
                            ((Wrapper)model).SelectIncompleto = true;

                    }
                }
                else if (pi.GetCustomAttributes(typeof(WrapperLoockupAttribute), false).Length > 0)
                {
                    Wrapper loockup = pi.PropertyType.GetConstructor(Type.EmptyTypes).Invoke(new object[0]) as Wrapper;
                    loockup.FromDataRow(dataRow);
                }
            }
        }

        public static bool ModelFromDataReader(DbDataReader reader, Object model)
        {
            if (reader.IsClosed)
                return false;

            if (!reader.Read())
            {
                reader.Close();
                return false;
            }

            Wrapper mbObj = model as Wrapper;
            if (mbObj != null)
                mbObj.IsNew = false;

            Dictionary<string, string> colunas = new Dictionary<string, string>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                string col = reader.GetName(i);
                colunas[col] = col;
            }

            foreach (PropertyInfo pi in model.GetType().GetProperties())
            {
                if (colunas.ContainsKey(pi.Name)
                    && (pi.GetCustomAttributes(typeof(WrapperPropertyAttribute), false).Length > 0
                    || pi.GetCustomAttributes(typeof(WrapperCalcPropertyAttribute), false).Length > 0))
                {
                    int col = reader.GetOrdinal(pi.Name);
                    if (col >= 0)
                    {
                        object vl = reader.GetValue(col);
                        if (vl is DBNull) vl = null;
                        if (pi.GetCustomAttributes(typeof(WrapperPropertyAttribute), false).Length > 0)
                        {
                            if (vl is Int64 && DataAccessManager.Default.ProviderName.Contains("SQLite") && pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false).Length > 0)
                            {
                                vl = Convert.ToInt32((Int64)vl);
                            }
                            pi.SetValue(model, vl, null);
                        }
                    }
                }
                else if (pi.GetCustomAttributes(typeof(WrapperLoockupAttribute), false).Length > 0)
                {
                    Wrapper loockup = pi.PropertyType.GetConstructor(Type.EmptyTypes).Invoke(new object[0]) as Wrapper;
                    loockup.FromDataReader(reader);
                }
            }

            return true;


        }

        public static void ModelToDataRow(Object model, DataRow dataRow)
        {
            foreach (PropertyInfo pi in model.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperPropertyAttribute), false).Length > 0)
                    dataRow[pi.Name] = pi.GetValue(model, null);
            }
        }

        public DbParameter CreateKeyParameter()
        {
            DbParameter param = DataAccessManager.Default.CreateParameter();
            param.SourceColumn = GetKeyName();
            param.Direction = ParameterDirection.Input;
            param.ParameterName = "@" + param.SourceColumn;
            param.Value = GetKeyValue();
            param.DbType = DbType.Int32;
            return param;
        }

        public static DbParameter[] ModelToDbParameters(Object model, bool addIdentity)
        {
            List<DbParameter> result = new List<DbParameter>();
            foreach (PropertyInfo pi in model.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperPropertyAttribute), false).Length > 0)
                {
                    object[] attrs = pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false);
                    if (attrs.Length > 0 && ((WrapperKeyPropertyAttribute)attrs[0]).Identity && !addIdentity)
                        continue;
                    DbParameter param = DataAccessManager.Default.CreateParameter("@" + pi.Name, pi.GetValue(model, null)
                        ?? DBNull.Value, DataAccessManager.Default.MapearTipoServidor(pi.PropertyType));
                    param.SourceColumn = pi.Name;
                    result.Add(param);
                }
            }
            return result.ToArray();
        }
        #endregion

        #region ConsultaPadrao

        public static T FindFirst<T>(string whereFormat, string orderBy, params object[] whereParams) where T : Wrapper, new()
        {
            T model = new T();

            orderBy = String.IsNullOrEmpty(orderBy) ? model.GenerateDefaultOrderBy() : orderBy;

            whereFormat = String.IsNullOrEmpty(whereFormat) ? "" : "WHERE " + whereFormat;
            orderBy = String.IsNullOrEmpty(orderBy) ? "" : "ORDER BY " + orderBy;

            whereFormat = string.Format(whereFormat, whereParams);

            DbDataReader reader = DataAccessManager.Default.ExecuteReader(
                String.Format("SELECT TOP 1 * FROM {0} {1} {2}",
                model.GetTableName(),
                whereFormat,
                orderBy));

            if (model.FromDataReader(reader))
            {
                reader.Close();
                return model;
            }
            else
            {
                reader.Close();
                return null;
            }


        }


        public static List<T> GetList<T>() where T : Wrapper, new()
        {
            return GetList<T>(String.Empty, String.Empty);
        }

        public static List<T> GetList<T>(string whereFormatString, params object[] parametros) where T : Wrapper, new()
        {
            return GetList<T>(whereFormatString, String.Empty, parametros);
        }

        public static List<T> GetList<T>(DataTable table) where T : Wrapper, new()
        {
            List<T> result = new List<T>();
            foreach (DataRow dr in table.Rows)
            {
                T item = new T();
                item.FromDataRow(dr);
                result.Add(item);
            }
            return result;
        }

        public static List<T> GetList<T>(string whereFormatString, string orderBy, params object[] parametros) where T : Wrapper, new()
        {
            return GetList<T>(GetDataTable<T>(whereFormatString, orderBy, parametros));
        }

        public static DataTable GetDataTable<T>() where T : Wrapper, new()
        {
            return GetDataTable<T>(String.Empty, String.Empty);
        }

        public static DataTable GetDataTable<T>(string whereFormatString, params object[] parametros) where T : Wrapper, new()
        {
            return GetDataTable<T>(whereFormatString, String.Empty, parametros);
        }

        public static DataTable GetDataTable<T>(string whereFormatString, string orderBy, params object[] parametros) where T : Wrapper, new()
        {
            T model = new T();
            List<T> result = new List<T>();

            orderBy = String.IsNullOrEmpty(orderBy) ? model.GenerateDefaultOrderBy() : orderBy;

            whereFormatString = String.IsNullOrEmpty(whereFormatString) ? "" : "WHERE " + whereFormatString;
            orderBy = String.IsNullOrEmpty(orderBy) ? "" : "ORDER BY " + orderBy;

            whereFormatString = string.Format(whereFormatString, parametros);

            return DataAccessManager.Default.GetDataTable(
                String.Format("SELECT * FROM {0} {1} {2}",
                model.GetTableName(),
                whereFormatString,
                orderBy));

        }

        protected virtual string GenerateDefaultOrderBy()
        {
            return this.GetNameProperties();
        }

        #endregion

        #region Validacao
        public bool Validar(List<String> mensagens)
        {
            return Validar(false, mensagens);
        }

        public bool Validar(bool recursive, List<String> mensagens)
        {
            bool result = ValidarInterno(mensagens);

            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperLoockupAttribute), false).Length > 0)
                {
                    Wrapper modelLoockup = pi.GetValue(this, null) as Wrapper;
                    result = result & modelLoockup.Validar(true, mensagens);
                }
            }

            return result;
        }
        protected virtual bool ValidarInterno(List<String> mensagens)
        {
            return true;
        }
        #endregion




        public virtual bool IsNew
        {
            get
            {
                if (HasIdentity())
                    return GetKeyValue() == 0;
                else
                    return _isNew;
            }

            protected set
            {
                _isNew = value;
            }

        }


        public virtual string GetKeyName()
        {
            if (_keyName == null)
            {
                foreach (PropertyInfo pi in this.GetType().GetProperties())
                {
                    object[] attrs = null;
                    if ((attrs = pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false)).Length > 0)
                    {
                        _hasIdentity = ((WrapperKeyPropertyAttribute)attrs[0]).Identity;
                        if (pi.PropertyType == typeof(int))
                            return pi.Name;
                    }
                }
                _keyName = "";
            }
            return _keyName;
        }


        public virtual bool HasIdentity()
        {
            if (_keyName == null)
            {
                GetKeyName();
            }
            return _hasIdentity;
        }


        public virtual int GetKeyValue()
        {
            int result = 0;
            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false).Length > 0)
                {
                    if (pi.PropertyType == typeof(int))
                        result = (int)pi.GetValue(this, null);
                }
            }
            return result;
        }
        public virtual void SetKeyValue(int id)
        {

            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperKeyPropertyAttribute), false).Length > 0)
                {
                    if (pi.PropertyType == typeof(int))
                        pi.SetValue(this, id, null);
                }
            }

        }
        public string GetTableName()
        {
            if (String.IsNullOrEmpty(_tableName))
            {
                object[] modelAttr = this.GetType().GetCustomAttributes(typeof(WrapperTableNameAttribute), true);
                if (modelAttr.Length > 0)
                {
                    _tableName = ((WrapperTableNameAttribute)modelAttr[0]).TableName;
                }
                else
                    _tableName = this.GetType().Name;
            }
            return _tableName;
        }

        public virtual void Save()
        {
            Save(false);
        }
        public virtual void Save(bool recursive)
        {
            List<String> mensagens = new List<string>();
            if (Validar(recursive, mensagens))
            {
                if (IsNew)
                {
                    Insert();
                }
                else
                {
                    Update();
                }


            }
            else
            {
                StringBuilder sb = new StringBuilder();
                mensagens.ForEach(a => sb.AppendLine(a));
                throw new WrapperBrokenRulesException(sb.ToString());
            }
        }

        /// <summary>
        /// Executa um insert desligando o identity insert.
        /// O valor do campo ID será aquele informado na instância.
        /// Nenhuma validação é realizada.
        /// </summary>
        public virtual void ReInsert()
        {
            DbParameter[] parameters = this.ToDbParameter(true);
            StringBuilder command = new StringBuilder();

            if (HasIdentity() && DataAccessManager.Default.ProviderName.Contains("SQLClient"))
            {
                command.AppendFormat("SET IDENTITY_INSERT {0} ON", GetTableName());
                command.AppendLine();
            }

            command.AppendFormat("INSERT INTO {0} ", GetTableName());
            command.AppendLine();
            command.Append("(");
            foreach (DbParameter param in parameters)
            {
                if (param != parameters[0])
                    command.Append(", ");

                command.Append(param.SourceColumn);
            }
            command.AppendLine(")");
            command.Append("VALUES (");
            foreach (DbParameter param in parameters)
            {
                if (param != parameters[0])
                    command.Append(", ");

                command.Append(param.ParameterName);
            }
            command.AppendLine(")");

            if (HasIdentity() && DataAccessManager.Default.ProviderName.Contains("SQLClient"))
            {

                command.AppendFormat("SET IDENTITY_INSERT {0} OFF", GetTableName());
                command.AppendLine();
            }

            DataAccessManager.Default.ExecuteNonQuery(command.ToString(), parameters);

        }



        protected virtual void Insert()
        {
            DbParameter[] parameters = this.ToDbParameter(false);
            StringBuilder command = new StringBuilder();

            command.AppendFormat("INSERT INTO {0} ", GetTableName());
            command.AppendLine();
            command.Append("(");
            foreach (DbParameter param in parameters)
            {
                if (param != parameters[0])
                    command.Append(", ");

                command.Append(param.SourceColumn);
            }
            command.AppendLine(")");
            command.Append("VALUES (");
            foreach (DbParameter param in parameters)
            {
                if (param != parameters[0])
                    command.Append(", ");

                command.Append(param.ParameterName);
            }
            command.AppendLine(")");

            if (HasIdentity())
            {
                int id = 0;
                if (DataAccessManager.Default.ProviderName.Contains("SQLite"))
                {
                    DataAccessManager.Default.ExecuteNonQuery(command.ToString(), parameters);
                    Int64 liteResult = (DataAccessManager.Default.ExecuteScalar(
                        String.Format("SELECT MAX({0}) FROM {1}", GetKeyName(), GetTableName())
                        , parameters) as Int64?).GetValueOrDefault(0);
                    id = Convert.ToInt32(liteResult);
                }
                else
                {
                    command.Append("SELECT @@IDENTITY");

                    decimal result = (DataAccessManager.Default.ExecuteScalar(command.ToString(), parameters) as decimal?).GetValueOrDefault(0);
                    id = Convert.ToInt32(result);
                }
                SetKeyValue(id);
            }
            else
            {
                DataAccessManager.Default.ExecuteNonQuery(command.ToString(), parameters);
            }
            IsNew = false;

        }


        protected virtual bool GetByID()
        {
            StringBuilder command = new StringBuilder();
            DbParameter keyParam = this.CreateKeyParameter();

            command.AppendFormat("SELECT TOP 1 * FROM {0} ", GetTableName());
            command.AppendFormat("WHERE {0} = {1}", keyParam.SourceColumn, keyParam.ParameterName);

            this.FromDataReader(DataAccessManager.Default.ExecuteReader(command.ToString(), keyParam));
            return !this.IsNew;
        }

        protected virtual bool GetFirstByWhere(string whereFormatString, params object[] parameters)
        {
            StringBuilder command = new StringBuilder();
            DbParameter keyParam = this.CreateKeyParameter();

            command.AppendFormat("SELECT TOP 1 * FROM {0} ", GetTableName());
            if (!String.IsNullOrEmpty(whereFormatString))
            {
                command.AppendLine(" WHERE ");
                command.AppendFormat(whereFormatString, parameters);
            }
            DbDataReader reader = DataAccessManager.Default.ExecuteReader(command.ToString(), keyParam);
            try
            {
                this.FromDataReader(reader);
            }
            finally
            {
                reader.Close();
            }
            return !this.IsNew;
        }

        protected virtual void Update()
        {
            DbParameter[] parameters = this.ToDbParameter();
            StringBuilder command = new StringBuilder();

            command.AppendFormat("UPDATE {0} SET", GetTableName());
            command.AppendLine();
            foreach (DbParameter param in parameters)
            {
                if (param != parameters[0])
                    command.Append(", ");

                command.AppendFormat("{0} = {1}", param.SourceColumn, param.ParameterName);
                command.AppendLine();
            }
            DbParameter keyParam = this.CreateKeyParameter();


            command.AppendFormat("WHERE {0} = {1}", keyParam.SourceColumn, keyParam.ParameterName);
            List<DbParameter> paramsCompleto = new List<DbParameter>(parameters);
            if (HasIdentity())
                paramsCompleto.Add(keyParam);
            DataAccessManager.Default.ExecuteNonQuery(command.ToString(), paramsCompleto.ToArray());
        }

        private DbParameter[] ToDbParameter()
        {
            return ToDbParameter(false);
        }
        private DbParameter[] ToDbParameter(bool addIdentity)
        {
            return ModelToDbParameters(this, addIdentity);
        }

        public virtual void Delete()
        {

            StringBuilder command = new StringBuilder();
            DbParameter param = this.CreateKeyParameter();

            command.AppendFormat("DELETE FROM {0} ", GetTableName());
            command.AppendLine();
            command.AppendFormat("WHERE {0} = {1}", param.SourceColumn, param.ParameterName);

            DataAccessManager.Default.ExecuteNonQuery(command.ToString(), param);

        }

        public string GetNameProperties()
        {
            StringBuilder descricao = new StringBuilder();
            bool first = true;
            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperNamePropertyAttribute), false).Length == 0)
                    continue;

                if (first)
                    first = false;
                else
                    descricao.Append(", ");

                descricao.Append(pi.Name);
            }

            return descricao.ToString();
        }

        public override string ToString()
        {
            StringBuilder descricao = new StringBuilder();
            bool first = true;
            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                if (pi.GetCustomAttributes(typeof(WrapperNamePropertyAttribute), false).Length == 0)
                    continue;


                if (first)
                    first = false;
                else
                    descricao.Append(", ");

                descricao.Append(pi.GetValue(this, null));
            }

            return descricao.ToString();
        }


    }

}


